﻿using System;
using System.Collections.Generic;
using System.Globalization;

namespace Red.Toolkit.EngineLogsViewer.Data.Types
{
    public static class LogEntryParser
    {
        public static SeverityLevel ExtractSeverityLevel(ref string logText)
        {
            var tokenIndex = logText.IndexOf("]");
            if (tokenIndex == -1)
            {
                return SeverityLevel.Unknown;
            }

            var severityText = logText.Substring(1, tokenIndex - 1);

            tokenIndex++;   // eat separator character
            logText = logText.Substring(tokenIndex, logText.Length - tokenIndex);

            return ParseSeverityLevel(severityText);
        }

        public static SeverityLevel ParseSeverityLevel(string severityText)
        {
            try
            {
                return (SeverityLevel)Enum.Parse(typeof(SeverityLevel), severityText);
            }
            catch (Exception)
            {
                return SeverityLevel.Unknown;
            }
        }

        public static SeverityLevel ParseSeverityLevel(uint severityLevel)
        {
            if (Enum.GetValues(typeof(SeverityLevel)).Length > severityLevel)
                return (SeverityLevel)severityLevel;
            else
                return SeverityLevel.Unknown;
        }

        public static string ExtractCategory(ref string newLogText)
        {
            var tokenIndex = newLogText.IndexOf(":");
            if (tokenIndex == -1)
            {
                return ""; // it is correct case, log could have no category
            }

            var categoryCandidate = newLogText.Substring(0, tokenIndex);
            if (categoryCandidate.IndexOf(' ') != -1)
                return ""; // it is not category name because substring has space

            var category = categoryCandidate;

            tokenIndex++;   // eat separator character
            newLogText = newLogText.Substring(tokenIndex, newLogText.Length - tokenIndex);

            return category;
        }

        public static string ExtractRawLogText(string newLogText)
        {
            return newLogText.Trim(new char[] { ' ', '\r', '\n', '\t' });
        }

        public static EngineLogGroupControlCode ExtractGroupControlCodeFromCategory(ref string category)
        {
            category = category.Substring(1, category.Length - 1);    // skip # character

            var startTokenIndex = category.IndexOf("(");
            var endTokenIndex = category.IndexOf(")");
            if (startTokenIndex == -1 || endTokenIndex == -1)
            {
                return EngineLogGroupControlCode.ParseError;
            }

            var groupControlCodeText = category.Substring(0, startTokenIndex);

            startTokenIndex++;  // skip separator character
            category = category.Substring(startTokenIndex, endTokenIndex - startTokenIndex);

            switch (groupControlCodeText)
            {
                case "enter":
                    return EngineLogGroupControlCode.CreateGroup;
                case "exit":
                    return EngineLogGroupControlCode.EndGroup;
            }

            return EngineLogGroupControlCode.ParseError;
        }

        public static List<EngineLogAbstractEntry> ParseEntriesFromPlainText(string text)
        {
            List<EngineLogAbstractEntry> entries = new List<EngineLogAbstractEntry>();
            EngineLogGroupEntry activeGroupEntry = null;

            var lines = text.Split(new[] { '\r', '\n' });
            for (int i = 0; i < lines.Length; ++i)
            {
                var line = ExtractRawLogText(lines[i]);
                if (line.Length == 0)
                    continue;

                // special case for groups
                if (line[0] == '#')
                {
                    if (line == "#StartGroup")
                    {
                        activeGroupEntry = new EngineLogGroupEntry();
                        entries.Add(activeGroupEntry);
                    }
                    else if (line == "#EndGroup")
                    {
                        activeGroupEntry = null;
                    }
                    continue;
                }


                var newLogEntry = new EngineLogSingleEntry();

                // parse date time
                var tokenIndex = line.IndexOf(']');
                var dateTimeText = line.Substring(1, tokenIndex - 1);
                newLogEntry.Timestamp = DateTime.ParseExact(dateTimeText, "MM/dd/yyyy HH:mm:ss", CultureInfo.InvariantCulture);

                tokenIndex++;   // eat separator
                line = line.Substring(tokenIndex, line.Length - tokenIndex);

                // parse thread id
                tokenIndex = line.IndexOf(']');
                var threadIdText = line.Substring(1, tokenIndex - 1);
                var sep = threadIdText.IndexOf(':');
                sep++;  // eat separator
                newLogEntry.ThreadId = uint.Parse(threadIdText.Substring(sep, threadIdText.Length - sep));

                tokenIndex++;   // eat separator
                line = line.Substring(tokenIndex, line.Length - tokenIndex);

                // parse severity
                tokenIndex = line.IndexOf(']');
                newLogEntry.Severity = ParseSeverityLevel(line.Substring(1, tokenIndex - 1));

                tokenIndex++;   // eat separator
                line = line.Substring(tokenIndex, line.Length - tokenIndex);

                // parse category
                tokenIndex = line.IndexOf(':');
                if (tokenIndex > 0)
                    newLogEntry.Category = line.Substring(0, tokenIndex);
                else
                    newLogEntry.Category = "";

                tokenIndex++;   // eat separator
                line = line.Substring(tokenIndex, line.Length - tokenIndex);

                // log message
                newLogEntry.Text = line.Trim();

                // add to proper container
                if (activeGroupEntry != null)
                    activeGroupEntry.InternalLogEntries.Add(newLogEntry);
                else
                    entries.Add(newLogEntry);
            }

            return entries;
        }
    }
}
